package ast;

import java.io.PrintStream;
import exceptions.DivByZeroException;
import java.util.Scanner;
import java.util.Map;
import java.util.Stack;

public class BinaryExpr extends Expr {
	public static final int PLUS = 1;
	public static final int MINUS = 2;
	public static final int TIMES = 3;
	public static final int DIV = 4;
	public final Expr expr1, expr2;
	public final int op;

	public BinaryExpr(Expr e1, int oper, Expr e2, Location loc) {
		super(loc);
		expr1 = e1;
		expr2 = e2;
		op = oper;
	}

	public void print(PrintStream ps) {
		ps.print("(");
		expr1.print(ps);
		switch (op) {
			case PLUS:
				ps.print("+");
				break;
			case MINUS:
				ps.print("-");
				break;
			case TIMES:
				ps.print("*");
				break;
			case DIV:
				ps.print("/");
				break;
		}
		expr2.print(ps);
		ps.print(")");
	}

	// interpret: evaluate the binary expression
	public AbstractValue evaluate(Stack<Map<String, AbstractValue>> stateStack, Scanner scanner) throws Exception {
		// get the value of the two expressions both sides of the operator
		AbstractValue n1abstractValue = expr1.evaluate(stateStack, scanner);
		AbstractValue n2abstractValue = expr2.evaluate(stateStack, scanner);

		// evaluate the binary expression
		switch (op) {
			// +
			case PLUS:
				/** NegFloat/NegInt + NegFloat/NegInt = NegFloat/NegInt, 
				 * NegFloat/NegInt + ZeroInt/ZeroFloat = NegInt/NegFloat, 
				 * ZeroInt/ZeroFloat + ZeroInt/ZeroFloat = ZeroInt/ZeroFloat, 
				 * PosInt/PosFloat + NegInt/NegFloat = AnyInt/AnyFloat,
				 * PosInt/PosFloat + ZeroInt/ZeroFloat = PosInt/PosFloat,
				 * PosInt/PosFloat + PosInt/PosFloat = PosInt/PosFloat
				 * AnyInt/AnyFloat + (PosInt/PosFloat)/(ZeroInt/ZeroFloat)/(NegInt/NegFloat)/(AnyInt/AnyFloat) = AnyInt/AnyFloat
				 * */ 
				switch (n1abstractValue.getType()){
					case NegInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.NegInt;
							case ZeroInt: return AbstractValue.NegInt;
							case PosInt: return AbstractValue.AnyInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case NegFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.NegFloat;
							case ZeroFloat: return AbstractValue.NegFloat;
							case PosFloat: return AbstractValue.AnyFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case ZeroInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.NegInt;
							case ZeroInt: return AbstractValue.ZeroInt;
							case PosInt: return AbstractValue.PosInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case ZeroFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.NegFloat;
							case ZeroFloat: return AbstractValue.ZeroFloat;
							case PosFloat: return AbstractValue.PosFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case PosInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.AnyInt;
							case ZeroInt: return AbstractValue.PosInt;
							case PosInt: return AbstractValue.PosInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case PosFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.AnyFloat;
							case ZeroFloat: return AbstractValue.PosFloat;
							case PosFloat: return AbstractValue.PosFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case AnyInt:
						return AbstractValue.AnyInt;
					case AnyFloat:
						return AbstractValue.AnyFloat;
				}
			// -
			case MINUS:
				/**
				 * If any of the operands is AnyInt/AnyFloat, the result is AnyInt/AnyFloat
				 * 
				 * NegFloat/NegInt - NegFloat/NegInt = AnyFloat/AnyInt, //
				 * ZeroInt/ZeroFloat - NegInt/NegFloat = PosInt/PosFloat,
				 * PosInt/PosFloat - NegInt/NegFloat = PosInt/PosFloat,
				 * AnyInt/AnyFloat - NegInt/NegFloat = AnyInt/AnyFloat,
				 * 
				 * NegFloat/NegInt - ZeroInt/ZeroFloat = NegFloat/NegInt,
				 * ZeroInt/ZeroFloat - ZeroInt/ZeroFloat = ZeroInt/ZeroFloat,
				 * PosInt/PosFloat - ZeroInt/ZeroFloat = PosInt/PosFloat,
				 * AnyInt/AnyFloat - ZeroInt/ZeroFloat = AnyInt/AnyFloat,
				 * 
				 * NegFloat/NegInt - PosInt/PosFloat = NegFloat/NegInt,
				 * ZeroInt/ZeroFloat - PosInt/PosFloat = NegInt/NegFloat,
				 * PosInt/PosFloat - PosInt/PosFloat = AnyInt/AnyFloat, //
				 * AnyInt/AnyFloat - PosInt/PosFloat = AnyInt/AnyFloat,
				 * 
				 * (NegFloat/NegInt) / (ZeroInt/ZeroFloat) / (PosInt/PosFloat) / (AnyInt/AnyFloat) - AnyInt/AnyFloat = AnyInt/AnyFloat
				 */
				switch (n1abstractValue.getType()){
					case NegInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.AnyInt;
							case ZeroInt: return AbstractValue.NegInt;
							case PosInt: return AbstractValue.NegInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case NegFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.AnyFloat;
							case ZeroFloat: return AbstractValue.NegFloat;
							case PosFloat: return AbstractValue.NegFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case ZeroInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.PosInt;
							case ZeroInt: return AbstractValue.ZeroInt;
							case PosInt: return AbstractValue.NegInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case ZeroFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.PosFloat;
							case ZeroFloat: return AbstractValue.ZeroFloat;
							case PosFloat: return AbstractValue.NegFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case PosInt:
						switch (n2abstractValue.getType()){
							case NegInt: return AbstractValue.PosInt;
							case ZeroInt: return AbstractValue.PosInt;
							case PosInt: return AbstractValue.AnyInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case PosFloat:
						switch (n2abstractValue.getType()){
							case NegFloat: return AbstractValue.PosFloat;
							case ZeroFloat: return AbstractValue.PosFloat;
							case PosFloat: return AbstractValue.AnyFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case AnyInt:
						return AbstractValue.AnyInt;
					case AnyFloat:
						return AbstractValue.AnyFloat;
				}
			// x
			case TIMES:
				/**
				 * If any of the operands is zero, the result is zero
				 * If any of the operands is AnyInt/AnyFloat, the result is AnyInt/AnyFloat
				 * 
				 * NegInt/NegFloat * NegInt/NegFloat = PosInt/PosFloat,
				 * 
				 * NegInt/NegFloat * PosInt/PosFloat = NegInt/NegFloat,
				 * PosInt/PosFloat * PosInt/PosFloat = PosInt/PosFloat,
				 * 
				 */
				switch(n1abstractValue.getType()){
					case NegInt:
						switch(n2abstractValue.getType()){
							case NegInt: return AbstractValue.PosInt;
							case ZeroInt: return AbstractValue.ZeroInt;
							case PosInt: return AbstractValue.NegInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case NegFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: return AbstractValue.PosFloat;
							case ZeroFloat: return AbstractValue.ZeroFloat;
							case PosFloat: return AbstractValue.NegFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case ZeroInt:
						return AbstractValue.ZeroInt;
					case ZeroFloat:
						return AbstractValue.ZeroFloat;

					case PosInt:
						switch(n2abstractValue.getType()){
							case NegInt: return AbstractValue.NegInt;
							case ZeroInt: return AbstractValue.ZeroInt;
							case PosInt: return AbstractValue.PosInt;
							case AnyInt: return AbstractValue.AnyInt;
							default: break;
						} break;
					case PosFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: return AbstractValue.NegFloat;
							case ZeroFloat: return AbstractValue.ZeroFloat;
							case PosFloat: return AbstractValue.PosFloat;
							case AnyFloat: return AbstractValue.AnyFloat;
							default: break;
						} break;

					case AnyInt:
						return AbstractValue.AnyInt;
					case AnyFloat:
						return AbstractValue.AnyFloat;
				}
			// ÷
			case DIV:
			/**
			 *  
			 * If the first operand is Zero, the result is Zero
			 * If the first operand is AnyInt/AnyFloat, the result is AnyInt/AnyFloat
			 * 
			 * If the second operand is Zero, throw a DivByZeroException
			 * If the second operand is AnyInt/AnyFloat, throw a DivByZeroException
			 * 
			 * Integers:
			 * NegInt / NegInt = AnyInt,
			 * NegInt / PosInt = AnyInt,
			 * 
			 * PosInt / NegInt = AnyInt,
			 * PosInt / PosInt = AnyInt,
			 * 
			 * FLoats:
			 * NegFloat / NegFloat = PosFloat,
			 * NegFloat / PosFloat = NegFloat,
			 * 
			 * PosFloat / NegFloat = NegFloat,
			 * PosFloat / PosFloat = PosFloat,
			 */
				switch(n1abstractValue.getType()){
					case NegInt:
						switch(n2abstractValue.getType()){
							case NegInt: case PosInt: return AbstractValue.AnyInt;
							case ZeroInt: case AnyInt: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case ZeroInt:
						switch(n2abstractValue.getType()){
							case NegInt: case PosInt: return AbstractValue.ZeroInt;
							case ZeroInt: case AnyInt: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case PosInt:
						switch(n2abstractValue.getType()){
							case NegInt: case PosInt: return AbstractValue.AnyInt;
							case ZeroInt: case AnyInt: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case AnyInt:
						switch(n2abstractValue.getType()){
							case NegInt: case PosInt: return AbstractValue.AnyInt;
							case ZeroInt: case AnyInt: throw new DivByZeroException("Division by zero");
							default: break;
						} break;

					case NegFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: return AbstractValue.PosFloat;
							case PosFloat: return AbstractValue.NegFloat;	
							case ZeroFloat: case AnyFloat: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case ZeroFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: case PosFloat: return AbstractValue.ZeroFloat;
							case ZeroFloat: case AnyFloat: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case PosFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: return AbstractValue.NegFloat;
							case PosFloat: return AbstractValue.PosFloat;
							case ZeroFloat: case AnyFloat: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					case AnyFloat:
						switch(n2abstractValue.getType()){
							case NegFloat: case PosFloat: return AbstractValue.AnyFloat;
							case ZeroFloat: case AnyFloat: throw new DivByZeroException("Division by zero");
							default: break;
						} break;
					
					default: break;
				}
		}
		return null;
	}
}
